# -*- coding: utf-8 -*-
"""
    xfree.admin.forms
    ~~~~~~~~~~~~~~~~~~~~~~~~

    该模块提供了管理视图所需的表单。

"""

import logging
# 导入日志模块，用于记录程序运行中的相关信息

from flask_allows import Permission
# 用于权限管理相关操作

from flask_babelplus import lazy_gettext as _
# 用于处理国际化文本，这里使用了懒加载获取文本的方式

from flask_wtf import FlaskForm
# Flask - WTF库中的表单基类

from sqlalchemy.orm.session import make_transient, make_transient_to_detached
# 用于处理SQLAlchemy会话中的对象状态转换

from wtforms import (
    BooleanField,
    DateField,
    HiddenField,
    IntegerField,
    PasswordField,
    StringField,
    SubmitField,
    TextAreaField,
)
# 导入各种表单字段类型

from wtforms.validators import (
    URL,
    DataRequired,
    Email,
    Length,
    Optional,
    ValidationError,
    regexp,
)
# 导入表单验证相关的函数和类

from wtforms_sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
# 用于创建基于SQLAlchemy查询的表单字段

from xfree.extensions import db
# 从xfree.extensions模块导入数据库对象

from xfree.forum.models import Category, Forum
# 导入论坛相关的分类和论坛模型

from xfree.user.models import Group, User
# 导入用户相关的用户组和用户模型

from xfree.utils.helpers import check_image
# 用于检查图片相关的工具函数

from xfree.utils.requirements import IsAtleastModerator
# 可能是用于判断用户是否至少是版主权限的模块


logger = logging.getLogger(__name__)
# 获取当前模块名对应的日志记录器


USERNAME_RE = r"^[\w.+-]+$"
is_username = regexp(
    USERNAME_RE, message=_("You can only use letters, numbers or dashes.")
)
# 定义用户名的正则表达式验证规则，验证失败时显示相应的国际化文本提示


def selectable_forums():
    return Forum.query.order_by(Forum.position)
# 定义函数，用于获取按位置排序的论坛查询结果，供表单字段使用


def selectable_categories():
    return Category.query.order_by(Category.position)
# 定义函数，用于获取按位置排序的分类查询结果，供表单字段使用


def selectable_groups():
    return Group.query.order_by(Group.id.asc()).all()
# 定义函数，用于获取按ID升序排序的用户组查询结果，供表单字段使用


def select_primary_group():
    return Group.query.filter(Group.guest!= True).order_by(Group.id)
# 定义函数，用于获取非访客用户组（guest为False）并按ID排序的查询结果，供表单字段使用


class UserForm(FlaskForm):
    # 用户表单类，继承自FlaskForm
    username = StringField(
        _("Username"),
        validators=[
            DataRequired(message=_("A valid username is required.")),
            is_username,
        ],
    )
    # 用户名表单字段，必填且需符合用户名正则表达式验证

    email = StringField(
        _("Email address"),
        validators=[
            DataRequired(message=_("A valid email address is required.")),
            Email(message=_("Invalid email address.")),
        ],
    )
    # 电子邮件表单字段，必填且需符合电子邮件格式验证

    password = PasswordField("Password", validators=[DataRequired()])
    # 密码表单字段，必填

    birthday = DateField(_("Birthday"), format="%Y-%m-%d", validators=[Optional()])
    # 生日表单字段，可选，日期格式为%Y-%m-%d

    gender = StringField(_("Gender"), validators=[Optional()])
    # 性别表单字段，可选

    location = StringField(_("Location"), validators=[Optional()])
    # 位置表单字段，可选

    website = StringField(_("Website"), validators=[Optional(), URL()])
    # 网站表单字段，可选且需符合URL格式验证

    avatar = StringField(_("Avatar"), validators=[Optional(), URL()])
    # 头像表单字段，可选且需符合URL格式验证

    signature = TextAreaField(_("Forum signature"), validators=[Optional()])
    # 论坛签名表单字段，可选

    notes = TextAreaField(_("Notes"), validators=[Optional(), Length(min=0, max=5000)])
    # 备注表单字段，可选，长度限制在0到5000字符之间

    activated = BooleanField(_("Is active?"), validators=[Optional()])
    # 是否激活表单字段，可选

    primary_group = QuerySelectField(
        _("Primary group"), query_factory=select_primary_group, get_label="name"
    )
    # 主要用户组表单字段，通过查询工厂函数获取数据，显示名称

    secondary_groups = QuerySelectMultipleField(
        _("Secondary groups"),
        # TODO: Template rendering errors "NoneType is not callable"
        #       without this, figure out why.
        query_factory=select_primary_group,
        get_label="name",
    )
    # 次要用户组表单字段，通过查询工厂函数获取数据，显示名称
    # 此处有TODO注释，表示存在模板渲染错误问题待解决

    submit = SubmitField(_("Save"))
    # 提交按钮表单字段

    def validate_username(self, field):
        if hasattr(self, "user"):
            user = User.query.filter(
                db.and_(
                    User.username.like(field.data.lower()),
                    db.not_(User.id == self.user.id),
                )
            ).first()
        else:
            user = User.query.filter(User.username.like(field.data.lower())).first()
        if user:
            raise ValidationError(_("This username is already taken."))
    # 验证用户名是否已被占用，如果存在相同用户名（不包括自身，若存在self.user属性）的用户，则抛出验证错误

    def validate_email(self, field):
        if hasattr(self, "user"):
            user = User.query.filter(
                db.and_(
                    User.email.like(field.data.lower()),
                    db.not_(User.id == self.user.id),
                )
            ).first()
        else:
            user = User.query.filter(User.email.like(field.data.lower())).first()
        if user:
            raise ValidationError(_("This email address is already taken."))
    # 验证电子邮件是否已被占用，如果存在相同电子邮件（不包括自身，若存在self.user属性）的用户，则抛出验证错误

    def validate_avatar(self, field):
        if field.data is not None:
            error, status = check_image(field.data)
            if error is not None:
                raise ValidationError(error)
            return status
    # 验证头像，如果头像数据不为空，通过check_image函数检查头像，若有错误则抛出验证错误，并返回状态

    def save(self):
        data = self.data
        data.pop("submit", None)
        data.pop("csrf_token", None)
        user = User(**data)
        return user.save()
    # 保存用户数据，先移除提交按钮和CSRF令牌数据，然后创建用户对象并调用其保存方法


class AddUserForm(UserForm):
    pass
# 添加用户表单类，继承自UserForm，没有额外属性或方法


class EditUserForm(UserForm):
    password = PasswordField("Password", validators=[Optional()])
    # 编辑用户表单中的密码字段，可选

    def __init__(self, user, *args, **kwargs):
        self.user = user
        kwargs["obj"] = self.user
        UserForm.__init__(self, *args, **kwargs)
    # 初始化方法，接收用户对象，设置self.user属性，并将用户对象传递给父类初始化方法


class GroupForm(FlaskForm):
    name = StringField(
        _("Group name"),
        validators=[DataRequired(message=_("Please enter a name for the group."))],
    )
    # 用户组名称表单字段，必填

    description = TextAreaField(_("Description"), validators=[Optional()])
    # 用户组描述表单字段，可选

    admin = BooleanField(
        _("Is 'Admin' group?"),
        description=_("With this option the group has access to " "the admin panel."),
    )
    # 是否是'Admin'组表单字段，有相应的描述

    super_mod = BooleanField(
        _("Is 'Super Moderator' group?"),
        description=_(
            "Check this, if the users in this group are allowed to "
            "moderate every forum."
        ),
    )
    # 是否是'Super Moderator'组表单字段，有相应的描述

    mod = BooleanField(
        _("Is 'Moderator' group?"),
        description=_(
            "Check this, if the users in this group are allowed to "
            "moderate specified forums."
        ),
    )
    # 是否是'Moderator'组表单字段，有相应的描述

    banned = BooleanField(
        _("Is 'Banned' group?"),
        description=_("Only one group of type 'Banned' is allowed."),
    )
    # 是否是'Banned'组表单字段，有相应的描述

    guest = BooleanField(
        _("Is 'Guest' group?"),
        description=_("Only one group of type 'Guest' is allowed."),
    )
    # 是否是'Guest'组表单字段，有相应的描述

    editpost = BooleanField(
        _("Can edit posts"),
        description=_("Check this, if the users in this group can edit posts."),
    )
    # 用户组是否能编辑帖子表单字段，有相应的描述

    deletepost = BooleanField(
        _("Can delete posts"),
        description=_("Check this, if the users in this group can delete " "posts."),
    )
    # 用户组是否能删除帖子表单字段，有相应的描述

    deletetopic = BooleanField(
        _("Can delete topics"),
        description=_("Check this, if the users in this group can delete " "topics."),
    )
    # 用户组是否能删除主题表单字段，有相应的描述

    posttopic = BooleanField(
        _("Can create topics"),
        description=_("Check this, if the users in this group can create " "topics."),
    )
    # 用户组是否能创建主题表单字段，有相应的描述

    postreply = BooleanField(
        _("Can post replies"),
        description=_("Check this, if the users in this group can post " "replies."),
    )
    # 用户组是否能回复帖子表单字段，有相应的描述

    mod_edituser = BooleanField(
        _("Moderators can edit user profiles"),
        description=_(
            "Allow moderators to edit another user's profile "
            "including password and email changes."
        ),
    )
    # 版主是否能编辑用户资料表单字段，有相应的描述

    mod_banuser = BooleanField(
        _("Moderators can ban users"),
        description=_("Allow moderators to ban other users."),
    )
    # 版主是否能封禁用户表单字段，有相应的描述

    viewhidden = BooleanField(
        _("Can view hidden posts and topics"),
        description=_("Allows a user to view hidden posts and topics"),
    )
    # 用户是否能查看隐藏帖子和主题表单字段，有相应的描述

    makehidden = BooleanField(
        _("Can hide posts and topics"),
        description=_("Allows a user to hide posts and topics"),
    )
    # 用户是否能隐藏帖子和主题表单字段，有相应的描述

    submit = SubmitField(_("Save"))
    # 提交按钮表单字段

    def validate_name(self, field):
        if hasattr(self, "group"):
            group = Group.query.filter(
                db.and_(
                    Group.name.like(field.data.lower()),
                    db.not_(Group.id == self.group.id),
                )
            ).first()
        else:
            group = Group.query.filter(Group.name.like(field.data.lower())).first()
        if group:
            raise ValidationError(_("This group name is already taken."))
    # 验证用户组名称是否已被占用，如果存在相同名称（不包括自身，若存在self.group属性）的用户组，则抛出验证错误

    def validate_banned(self, field):
        if hasattr(self, "group"):
            group = Group.query.filter(
                db.and_(Group.banned, db.not_(Group.id == self.group.id))
            ).count()
        else:
            group = Group.query.filter_by(banned=True).count()
        if field.data and group > 0:
            raise ValidationError(_("There is already a group of type " "'Banned'."))
    # 验证是否存在多个'Banned'类型的用户组，如果当前表单中的'banned'字段为True且已存在'Banned'组（不包括自身，若存在self.group属性），则抛出验证错误

    def validate_guest(self, field):
        if hasattr(self, "group"):
            group = Group.query.filter(
                db.and_(Group.guest, db.not_(Group.id == self.group.id))
            ).count()
        else:
            group = Group.query.filter_by(guest=True).count()
        if field.data and group > 0:
            raise ValidationError(_("There is already a group of type " "'Guest'."))
    # 验证是否存在多个'Guest'类型的用户组，如果当前表单中的'guest'字段为True且已存在'Guest'组（不包括自身，若存在self.group属性），则抛出验证错误

    def validate(self):
        if not super(GroupForm, self).validate():
            return False
        result = True
        permission_fields = (
            self.editpost,
            self.deletepost,
            self.deletetopic,
            self.posttopic,
            self.postreply,
            self.mod_edituser,
            self.mod_banuser,
            self.viewhidden,
            self.makehidden,
        )
        group_fields = [self.admin, self.super_mod, self.mod, self.banned, self.guest]
        # 我们不允许修改访客用户组的任何权限
        if self.guest.data:
            for field in permission_fields:
                if field.data:
                    # 如果在'validate_guest'中进行此操作，会在字段上显示此警告
                    field.errors.append(
                        _("Can't assign any permissions to this group.")
                    )
                    result = False
        checked = []
        for field in group_fields:
            if field.data and field.data in checked:
                if len(checked) > 1:
                    field.errors.append("A group can't have multiple group types.")
                    result = False
            else:
                checked.append(field.data)
        return result
    # 表单验证方法，首先调用父类验证方法，如果父类验证不通过则返回False。然后进行一系列用户组相关的验证逻辑，
    # 包括检查访客用户组权限设置是否正确，以及用户组类型是否重复等，根据验证结果返回True或False

    def save(self):
        data = self.data
        data.pop("submit", None)
        data.pop("csrf_token", None)
        group = Group(**data)
        return group.save()
    # 保存用户组数据，先移除提交按钮和CSRF令牌数据，然后创建用户组对象并调用其保存方法


class EditGroupForm(GroupForm):
    def __init__(self, group, *args, **kwargs):
        self.group = group
        kwargs["obj"] = self.group
        GroupForm.__init__(self, *args, **kwargs)
    # 编辑用户组表单类，继承自GroupForm，初始化方法接收用户组对象，设置self.group属性，并将用户组对象传递给父类初始化方法


class AddGroupForm(GroupForm):
    pass
# 添加用户组表单类，继承自GroupForm，没有额外属性或方法




class ForumForm(FlaskForm):
    # 论坛标题字段
    title = StringField(
        # 字段标签为'Forum title'，这里使用了国际化文本
        _("Forum title"),
        # 该字段必填，提示信息为'Please enter a forum title.'（国际化文本）
        validators=[DataRequired(message=_("Please enter a forum title."))]
    )

    # 论坛描述字段
    description = TextAreaField(
        # 字段标签为'Description'（国际化文本）
        _("Description"),
        # 该字段可选
        validators=[Optional()],
        # 提示可以使用Markdown格式化描述（国际化文本）
        description=_("You can format your description with Markdown.")
    )

    # 论坛位置字段
    position = IntegerField(
        # 字段标签为'Position'（国际化文本）
        _("Position"),
        # 该字段默认值为1
        default=1,
        # 该字段必填，提示信息为'Please enter a position for the forum.'（国际化文本）
        validators=[DataRequired(message=_("Please enter a position for the forum."))]
    )

    # 论坛所属分类字段
    category = QuerySelectField(
        # 字段标签为'Category'（国际化文本）
        _("Category"),
        # 查询分类的函数
        query_factory=selectable_categories,
        # 不允许该字段为空
        allow_blank=False,
        # 显示分类的标题作为选项标签
        get_label="title",
        # 提示信息为'The category that contains this forum.'（国际化文本）
        description=_("The category that contains this forum.")
    )

    # 外部链接字段
    external = StringField(
        # 字段标签为'External link'（国际化文本）
        _("External link"),
        # 该字段可选且必须是URL格式
        validators=[Optional(), URL()],
        # 提示信息为'A link to a website i.e. \'http://xfree.org\'.'（国际化文本）
        description=_("A link to a website i.e. 'http://xfree.org'.")
    )

    # 版主字段
    moderators = StringField(
        # 字段标签为'Moderators'（国际化文本）
        _("Moderators"),
        # 提示信息为'Comma separated usernames. Leave it blank if you do not want to set any moderators.'（国际化文本）
        description=_(
            "Comma separated usernames. Leave it blank if you do not want to set any moderators."
        )
    )

    # 是否显示版主字段
    show_moderators = BooleanField(
        # 字段标签为'Show moderators'（国际化文本）
        _("Show moderators"),
        # 提示信息为'Do you want to show the moderators on the index page?'（国际化文本）
        description=_("Do you want to show the moderators on the index page?")
    )

    # 论坛是否锁定字段
    locked = BooleanField(
        # 字段标签为'Locked?'（国际化文本）
        _("Locked?"),
        # 提示信息为'Disable new posts and topics in this forum.'（国际化文本）
        description=_("Disable new posts and topics in this forum.")
    )

    # 可访问论坛的用户组字段
    groups = QuerySelectMultipleField(
        # 字段标签为'Group access'（国际化文本）
        _("Group access"),
        # 查询用户组的函数
        query_factory=selectable_groups,
        # 显示用户组的名称作为选项标签
        get_label="name",
        # 提示信息为'Select the groups that can access this forum.'（国际化文本）
        description=_("Select the groups that can access this forum.")
    )

    submit = SubmitField(_("Save"))

    def validate_external(self, field):
        # 如果表单有forum属性
        if hasattr(self, "forum"):
            # 如果该论坛的主题数量大于0
            if self.forum.topics.count() > 0:
                # 则不允许将其转换为外部链接，抛出验证错误
                raise ValidationError(
                    _(
                        "You cannot convert a forum that contains topics into an external link."
                    )
                )

    def validate_show_moderators(self, field):
        # 如果要显示版主字段且没有设置版主
        if field.data and not self.moderators.data:
            # 抛出验证错误，提示需要设置版主
            raise ValidationError(_("You also need to specify some moderators."))

    def validate_moderators(self, field):
        approved_moderators = []
        # 如果版主字段有数据
        if field.data:
            # 将输入的逗号分隔的用户名进行处理
            moderators = [mod.strip() for mod in field.data.split(",")]
            # 查询对应的用户
            users = User.query.filter(User.username.in_(moderators))
            for user in users:
                # 如果用户不具有版主权限
                if not Permission(IsAtleastModerator, identity=user):
                    # 抛出验证错误，提示该用户不在版主组
                    raise ValidationError(
                        _("%(user)s is not in a moderators group.", user=user.username)
                    )
                else:
                    # 将有版主权限的用户添加到列表
                    approved_moderators.append(user)
        # 将版主字段的数据设置为经过验证的用户列表
        field.data = approved_moderators

    def save(self):
        data = self.data
        # 从表单数据中移除提交按钮和csrf_token数据
        data.pop("submit", None)
        data.pop("csrf_token", None)
        # 使用剩余数据创建Forum对象并保存
        forum = Forum(**data)
        return forum.save()


class EditForumForm(ForumForm):
    id = HiddenField()

    def __init__(self, forum, *args, **kwargs):
        self.forum = forum
        kwargs["obj"] = self.forum
        ForumForm.__init__(self, *args, **kwargs)

    def save(self):
        data = self.data
        # 从表单数据中移除提交按钮和csrf_token数据
        data.pop("submit", None)
        data.pop("csrf_token", None)
        forum = Forum(**data)
        # 处理SQLAlchemy对象的状态，使其可以合并
        make_transient(forum)
        make_transient_to_detached(forum)
        return forum.save()


class AddForumForm(ForumForm):
    pass


class CategoryForm(FlaskForm):
    # 分类标题字段
    title = StringField(
        # 字段标签为'Category title'（国际化文本）
        _("Category title"),
        # 该字段必填，提示信息为'Please enter a category title.'（国际化文本）
        validators=[DataRequired(message=_("Please enter a category title."))]
    )

    # 分类描述字段
    description = TextAreaField(
        # 字段标签为'Description'（国际化文本）
        _("Description"),
        # 该字段可选
        validators=[Optional()],
        # 提示可以使用Markdown格式化描述（国际化文本）
        description=_("You can format your description with Markdown.")
    )

    # 分类位置字段
    position = IntegerField(
        # 字段标签为'Position'（国际化文本）
        _("Position"),
        # 该字段默认值为1
        default=1,
        # 该字段必填，提示信息为'Please enter a position for the category.'（国际化文本）
        validators=[DataRequired(message=_("Please enter a position for the category."))]
    )

    submit = SubmitField(_("Save"))

    def save(self):
        data = self.data
        # 从表单数据中移除提交按钮和csrf_token数据
        data.pop("submit", None)
        data.pop("csrf_token", None)
        # 使用剩余数据创建Category对象并保存
        category = Category(**data)
        return category.save()
